﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using AeroWizard;
using Luminescence.Xiph;

namespace XiphMediaEncoder
{
   public partial class MainForm : Form
   {
      private readonly ScriptGenerator scripter;
      private TextBox txtBox;

      public MainForm()
      {
         InitializeComponent();
         scripter = new ScriptGenerator();

         cmdPcmToFlac.Tag = ConversionType.PcmToFlac;
         cmdPcmToVorbis.Tag = ConversionType.PcmToVorbis;
         cmdPcmToSpeex.Tag = ConversionType.PcmToSpeex;

         cmdFlacToPcm.Tag = ConversionType.FlacToPcm;
         cmdVorbisToPcm.Tag = ConversionType.VorbisToPcm;
         cmdSpeexToPcm.Tag = ConversionType.SpeexToPcm;

         cmdFlacToFlac.Tag = ConversionType.FlacToFlac;
         cmdFlacToVorbis.Tag = ConversionType.FlacToVorbis;

         cbxPriority.Text = "Normal";
         nudMaxParallelEncoding.Value = nudMaxParallelEncoding.Maximum = Environment.ProcessorCount;
         cbxAutoShutdown.SelectedIndex = 0;

         AddEventHandler();
      }

      private void AddEventHandler()
      {
         chkUseAllProcessor.CheckedChanged += (s, e) => nudMaxParallelEncoding.Enabled = !chkUseAllProcessor.Checked;

         cmdIgnoreSetupPowershell.Click += (s, e) => wizard.NextPage(taskPage);
         cmdSetupPowerShell.Click += (s, e) =>
         {
            scripter.SetupPowershell();
            if (scripter.CanRunScript)
               wizard.NextPage(taskPage);
         };

         cmdEncode.Click += (s, e) => wizard.NextPage(encodePage);
         cmdDecode.Click += (s, e) => wizard.NextPage(decodePage);
         cmdTranscode.Click += (s, e) => wizard.NextPage(transcodePage);

         cmdPcmToFlac.Click += cmdSetCodecConversion_Click;
         cmdPcmToVorbis.Click += cmdSetCodecConversion_Click;
         cmdPcmToSpeex.Click += cmdSetCodecConversion_Click;

         cmdFlacToPcm.Click += cmdSetCodecConversion_Click;
         cmdVorbisToPcm.Click += cmdSetCodecConversion_Click;
         cmdSpeexToPcm.Click += cmdSetCodecConversion_Click;

         cmdFlacToFlac.Click += cmdSetCodecConversion_Click;
         cmdFlacToVorbis.Click += cmdSetCodecConversion_Click;

         Load += (s, e) =>
         {
            try
            {
               scripter.CheckPrerequist();
            }
            catch (Exception ex)
            {
               MessageBox.Show(ex.Message, "Xiph Media Encoder", MessageBoxButtons.OK, MessageBoxIcon.Stop);
               Close();
            }

            if (scripter.CanRunScript)
               wizard.NextPage(taskPage);
         };

         wizard.Cancelling += (s, e) => Close();
         wizard.Finished += wizard_Finished;

         chkTagAutomatically.CheckedChanged += (s, e) =>
         {
            pnlTagAutomatically.Visible = chkTagAutomatically.Checked;
            CanGoToOutputStep();
         };

         chkRenameFiles.CheckedChanged += (s, e) =>
         {
            pnlRenameFiles.Visible = chkRenameFiles.Checked;
            CanGoToProcessStep();
         };

         chkRecursive.CheckedChanged += (s, e) => LoadInputFilePreview();

         patternContextMenu.Opening += patternContextMenu_Opening;
         btnSelectInputFolder.Click += btnSelectInputFolder_Click;
         btnSelectOutputFolder.Click += btnSelectOutputFolder_Click;

         tbxInputFilenamePattern.Validating += tbxInputFilenamePattern_Validating;
         tbxInputFolderPattern.Validating += tbxInputFolderPattern_Validating;
         cbxFilePreview.SelectedIndexChanged += (s, e) => PreviewTag();

         tbxOutputFilenamePattern.Validating += tbxOutputFilenamePattern_Validating;
         tbxOutputFolderPattern.Validating += tbxOutputFolderPattern_Validating;
         cbxFilePreview2.SelectedIndexChanged += (s, e) => PreviewRename();

         tbxScriptName.Validating += tbxScriptName_Validating;

         cutToolStripMenuItem.Click += (s, e) => txtBox.Cut();
         copyToolStripMenuItem.Click += (s, e) => txtBox.Copy();
         undoToolStripMenuItem.Click += (s, e) => txtBox.Undo();
         pasteToolStripMenuItem.Click += (s, e) => txtBox.Paste();
         selectAllToolStripMenuItem.Click += (s, e) => txtBox.SelectAll();
         deleteToolStripMenuItem.Click += (s, e) =>
         {
            int pos = txtBox.SelectionStart;
            txtBox.Text = txtBox.Text.Substring(0, pos) + txtBox.Text.Substring(pos + txtBox.SelectionLength);
            txtBox.SelectionStart = pos;
            txtBox.SelectionLength = 0;
         };

         inputPage.Initialize += inputPage_Initialize;
         inputPage.Commit += (s, e) => scripter.SetInputOptions(tbxSource.Text, chkRecursive.Checked, tbxInputFilenamePattern.Text, tbxInputFolderPattern.Text);

         outputPage.Initialize += outputPage_Initialize;
         outputPage.Commit += (s, e) => scripter.SetOutputOptions(trkQuality.Value, tbxOutputFilenamePattern.Text, tbxOutputFolderPattern.Text);

         taskPage.Initialize += (s, e) => taskPage.AllowBack = !scripter.CanRunScript;

         processPage.Initialize += processPage_Initialize;

         lnkAbout.LinkClicked += (s, e) =>
            {
               using (var af = new AboutForm())
                  af.ShowDialog();
            };
      }

      private void wizard_Finished(object sender, EventArgs e)
      {
         scripter.SetProcessOptions(chkUseAllProcessor.Checked ? 0 : (int)nudMaxParallelEncoding.Value, cbxPriority.Text, (AfterEncodingAction)cbxAutoShutdown.SelectedIndex, tbxScriptName.Text);

         try
         {
            scripter.SaveScript();

            if (chkRunScript.Checked)
               ScriptGenerator.RunScript(scripter.ScriptPath, chkKeepPowerShellOpen.Checked);

            Close();
         }
         catch (Exception ex)
         {
            MessageBox.Show(ex.GetBaseException().Message, "Xiph Media Encoder", MessageBoxButtons.OK, MessageBoxIcon.Error);
         }
      }
             
      private void LoadInputFilePreview()
      {
         cbxFilePreview.Items.Clear();
         cbxFilePreview2.Items.Clear();

         if (tbxSource.Text.Length == 0)
            return;

         string[] files = null;
         try
         {
            files = scripter.GetInputFiles(tbxSource.Text, chkRecursive.Checked);
         }
         catch (Exception ex)
         {
            MessageBox.Show(this, ex.GetBaseException().Message, "Xiph Media Encoder", MessageBoxButtons.OK, MessageBoxIcon.Error);
         }

         if (files != null && files.Length != 0)
         {
            cbxFilePreview.Items.AddRange(files);
            cbxFilePreview2.Items.AddRange(files);
            cbxFilePreview.SelectedIndex = cbxFilePreview2.SelectedIndex = 0;
         }

         PreviewTag();
         PreviewRename();
      }

      #region Input Step

      private void CanGoToOutputStep()
      {
         inputPage.AllowNext = tbxSource.Text.Length != 0 &&
            (!chkTagAutomatically.Checked ||
               (error.GetError(tbxInputFilenamePattern).Length == 0 &&
                error.GetError(tbxInputFolderPattern).Length == 0));
      }

      private void tbxInputFolderPattern_Validating(object sender, CancelEventArgs e)
      {
         error.SetError(tbxInputFolderPattern, TemplateValidator.ValidatePathTemplate(tbxInputFolderPattern.Text));
         PreviewTag();
         CanGoToOutputStep();
      }

      private void tbxInputFilenamePattern_Validating(object sender, CancelEventArgs e)
      {
         error.SetError(tbxInputFilenamePattern, TemplateValidator.ValidateFilenameTemplate(tbxInputFilenamePattern.Text));
         PreviewTag();
         CanGoToOutputStep();
      }

      private void btnSelectInputFolder_Click(object sender, EventArgs e)
      {
         var fbd = new FolderBrowserDialog();
         fbd.ShowNewFolderButton = false;
         fbd.Description = String.Format("Please locate your {0} files.", scripter.InputFileType);
         if (fbd.ShowDialog() == DialogResult.OK)
         {
            tbxSource.Text = fbd.SelectedPath;
            CanGoToOutputStep();
            LoadInputFilePreview();
         }
      }

      private void PreviewTag()
      {
         ltvTagPreview.Items.Clear();

         if (cbxFilePreview.Text.Length != 0 &&
             error.GetError(tbxInputFilenamePattern).Length == 0 &&
             error.GetError(tbxInputFolderPattern).Length == 0)
         {
            var tags = new SortedList<string, List<string>>();
            TagProcessor.TagFromFilename(tags, tbxInputFilenamePattern.Text, cbxFilePreview.Text);
            TagProcessor.TagFromPath(tags, tbxInputFolderPattern.Text, cbxFilePreview.Text);
            foreach (var kvp in tags)
            {
               foreach (string value in kvp.Value)
               {
                  ltvTagPreview.Items.Add(new ListViewItem(new[] { kvp.Key, value }));
               }
            }            
         }
      }

      private void inputPage_Initialize(object sender, WizardPageInitEventArgs e)
      {
         inputPage.Text = String.Format("Locate and setup your {0} files", scripter.InputFileType);
         pnlGlobalTagAutomatically.Visible =
            scripter.Conversion == ConversionType.PcmToFlac ||
            scripter.Conversion == ConversionType.PcmToVorbis ||
            scripter.Conversion == ConversionType.PcmToSpeex;
      }      

      #endregion

      #region Output Step

      private void CanGoToProcessStep()
      {
         outputPage.AllowNext = !chkRenameFiles.Checked ||
               (error.GetError(tbxOutputFilenamePattern).Length == 0 &&
                error.GetError(tbxOutputFolderPattern).Length == 0);
      }

      private void PreviewRename()
      {
         txtPreviewRename.Clear();

         if (cbxFilePreview2.Text.Length != 0 &&
             error.GetError(tbxOutputFilenamePattern).Length == 0 &&
             error.GetError(tbxOutputFolderPattern).Length == 0)
         {
            SortedList<string, List<string>> tags;
            switch (scripter.Conversion)
            {
               case ConversionType.PcmToFlac:
               case ConversionType.PcmToVorbis:
               case ConversionType.PcmToSpeex:
                  tags = new SortedList<string, List<string>>();
                  TagProcessor.TagFromFilename(tags, tbxInputFilenamePattern.Text, cbxFilePreview2.Text);
                  TagProcessor.TagFromPath(tags, tbxInputFolderPattern.Text, cbxFilePreview2.Text);
                  break;
               case ConversionType.FlacToPcm:
               case ConversionType.FlacToVorbis:
                  if (String.Compare(Path.GetExtension(cbxFilePreview2.Text), ".ogg", true) == 0)
                     goto case ConversionType.VorbisToPcm; 
                  var flac = new FlacTagger(cbxFilePreview2.Text).GetAllTags();
                  tags = new SortedList<string, List<string>>(flac);
                  break;
               case ConversionType.VorbisToPcm:
               case ConversionType.SpeexToPcm:
                  var ogg = new OggTagger(cbxFilePreview2.Text).GetAllTags();
                  tags = new SortedList<string, List<string>>(ogg);
                  break;
               default:
                  return;
            }

            string path = tbxOutputFolderPattern.Text.Length != 0 ? TagProcessor.PathFromTags(tbxOutputFolderPattern.Text, tags) : Path.GetDirectoryName(cbxFilePreview2.Text);
            string filename = tbxOutputFilenamePattern.Text.Length != 0 ? TagProcessor.FileNameFromTags(tbxOutputFilenamePattern.Text, tags) : Path.GetFileNameWithoutExtension(cbxFilePreview2.Text);
            int index = scripter.OutputFileType.IndexOf('.');
            string ext = scripter.OutputFileType.Substring(index, scripter.OutputFileType.Length - index - 1);
            txtPreviewRename.Text = Path.Combine(path, filename) + ext;
         }
      }

      private void tbxOutputFolderPattern_Validating(object sender, CancelEventArgs e)
      {
         error.SetError(tbxOutputFolderPattern, TemplateValidator.ValidateOrganizeTemplate(tbxOutputFolderPattern.Text));
         PreviewRename();
         CanGoToProcessStep();
      }

      private void tbxOutputFilenamePattern_Validating(object sender, CancelEventArgs e)
      {
         error.SetError(tbxOutputFilenamePattern, TemplateValidator.ValidateRenameTemplate(tbxOutputFilenamePattern.Text));
         PreviewRename();
         CanGoToProcessStep();
      }

      private void btnSelectOutputFolder_Click(object sender, EventArgs e)
      {
         var fbd = new FolderBrowserDialog();
         fbd.Description = String.Format("Where do you want to save your {0} files.", scripter.OutputFileType);
         if (fbd.ShowDialog() == DialogResult.OK)
            tbxOutputFolderPattern.Text = fbd.SelectedPath;
      }

      private void outputPage_Initialize(object sender, WizardPageInitEventArgs e)
      {
         outputPage.Text = String.Format("Destination and configuration of your {0} files", scripter.OutputFileType);

         if (scripter.Conversion == ConversionType.PcmToSpeex || 
            scripter.Conversion == ConversionType.PcmToVorbis || 
            scripter.Conversion == ConversionType.FlacToVorbis)
         {
            pnlQuality.Visible = true;
            pnlRenameOrganize.Location = new Point(3, 70);
            chkRenameFiles.Enabled = true;
         }
         else
         {
            pnlQuality.Visible = false;
            pnlRenameOrganize.Location = new Point(3, 3);

            if (scripter.Conversion == ConversionType.FlacToFlac)
               chkRenameFiles.Checked = chkRenameFiles.Enabled = false;
         }

         CanGoToProcessStep();
      }

      #endregion

      #region Process Step

      private void processPage_Initialize(object sender, WizardPageInitEventArgs e)
      {
         if (scripter.Conversion == ConversionType.FlacToPcm || 
            scripter.Conversion == ConversionType.VorbisToPcm || 
            scripter.Conversion == ConversionType.SpeexToPcm)
         {
            pnlProcessorOptions.Visible = false;
            pnlScriptOptions.Location = new Point(3, 3);
         }
         else
         {
            pnlProcessorOptions.Visible = true;
            pnlScriptOptions.Location = new Point(3, 79);
         }

         if (!scripter.CanRunScript)
            chkRunScript.Enabled = chkRunScript.Checked = chkKeepPowerShellOpen.Enabled = chkKeepPowerShellOpen.Checked = false;
         else
            chkKeepPowerShellOpen.Enabled = chkRunScript.Enabled = true;

         DateTime d = DateTime.Now;
         string month = d.Month < 10 ? "0" + d.Month.ToString() : d.Month.ToString();
         string day = d.Day < 10 ? "0" + d.Day.ToString() : d.Day.ToString();
         string hour = d.Hour < 10 ? "0" + d.Hour.ToString() : d.Hour.ToString();
         string minute = d.Minute < 10 ? "0" + d.Minute.ToString() : d.Minute.ToString();
         string second = d.Second < 10 ? "0" + d.Second.ToString() : d.Second.ToString();
         tbxScriptName.Text = String.Format("{0}_{1}-{2}-{3}_{4}{5}{6}.ps1", scripter.Conversion, d.Year, month, day, hour, minute, second);
      }

      private static string ValidateFilename(string filename)
      {
         if (filename.Trim().Length == 0)
            return "The filename cannot be empty.";

         var invalidFilenameChars = filename.Intersect(Path.GetInvalidFileNameChars()).ToArray();
         if (invalidFilenameChars.Length != 0)
            return String.Format("The filename contains the following invalid characters: {0}", String.Join(", ", invalidFilenameChars));

         if (!filename.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase))
            return "The filename must contain the PowerShell extension (.ps1).";

         return null;
      }

      private void tbxScriptName_Validating(object sender, CancelEventArgs e)
      {
         string er = ValidateFilename(tbxScriptName.Text);
         error.SetError(tbxScriptName, er);
         processPage.AllowNext = er == null;
      }

      #endregion

      private void patternContextMenu_Opening(object sender, CancelEventArgs e)
      {
         var cms = (ContextMenuStrip)sender;
         txtBox = (TextBox)cms.SourceControl;

         sepFakeToken.Visible = mnuFakeToken.Visible = 
            txtBox == tbxInputFilenamePattern || txtBox == tbxInputFolderPattern;

         cutToolStripMenuItem.Enabled = copyToolStripMenuItem.Enabled = deleteToolStripMenuItem.Enabled =
            txtBox.SelectionLength != 0;

         undoToolStripMenuItem.Enabled = txtBox.CanUndo;
         pasteToolStripMenuItem.Enabled = Clipboard.ContainsText();
         selectAllToolStripMenuItem.Enabled = txtBox.Text.Length != 0;
      }

      private void tokenToolStripMenuItem_Click(object sender, EventArgs e)
      {
         txtBox.Paste(((ToolStripMenuItem)sender).Text);
      }

      private void cmdSetCodecConversion_Click(object sender, EventArgs e)
      {
         scripter.Conversion = (ConversionType)((Control)sender).Tag;
         wizard.NextPage(inputPage);
      }
   }
}